package cn.weekendpi;


import cn.weekendpi.util.CodeFormatUtils;
import cn.weekendpi.util.IOUtils;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;

import java.io.*;
import java.util.*;


@Mojo(name = "CleanPrint", defaultPhase = LifecyclePhase.PACKAGE)
public class CleanPrint extends AbstractMojo {

    @Parameter(property = "CleanPrint.project", defaultValue = "${project}")
    private MavenProject project;

    private static List<File> javaFiles = Collections.synchronizedList(new ArrayList<File>());

    private static String statementSeparator = ";";

    public void execute() {
        long startTime = System.currentTimeMillis();
        File projectBasedir = project.getBasedir();
        if (!projectBasedir.exists()) {
            getLog().error(String.format("directory: %s is not exist.", projectBasedir));
            return;
        }
        if (!projectBasedir.isDirectory()) {
            getLog().error(String.format("%s is not a directory", projectBasedir));
            return;
        }

        IOUtils.listJavaFiles(projectBasedir, javaFiles);

        cleanPrintStatementParallel();

        long endTime = System.currentTimeMillis();
        getLog().info(String.format("process time: %s ms", endTime - startTime));
    }

    private void cleanPrintStatement() {
        try {
            for (File javaFile : javaFiles) {
                updateFileContent(javaFile);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void cleanPrintStatementParallel() {
        javaFiles.parallelStream().forEach(javaFile -> {
            try {
                updateFileContent(javaFile);
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
    }

    private synchronized void updateFileContent(File javaFile) throws IOException {
        FileReader fr = new FileReader(javaFile);
        BufferedReader bfr = new BufferedReader(fr);
        String lineData;
        StringBuffer newContent = new StringBuffer();
        while ((lineData = bfr.readLine()) != null) {
            if (isContainsPrintStatement(lineData)) {
                lineChange(lineData, newContent);
            } else {
                newContent.append(lineData).append(System.lineSeparator());
            }
        }
        fr.close();
        bfr.close();

        BufferedWriter bfw = new BufferedWriter(new FileWriter(javaFile));
        bfw.write(newContent.toString());
        bfw.close();
    }

    /**
     * whether is a print statement line
     *
     * @param line code line
     * @return whether is a print statement line
     */
    private boolean isContainsPrintStatement(String line) {
        boolean possibleContains = line.contains("System.out.println") || line.contains("System.out.print");
        if (possibleContains) {
            String code = CodeFormatUtils.transferParamToUUid(line, "\"", new HashMap<>());
            return code.contains("System.out.println") || code.contains("System.out.print");
        }
        return false;
    }

    /**
     * adds a comment to the specified line.
     *
     * @param line specified line data.
     * @param sb   a collection of received results.
     */
    private void lineChange(String line, StringBuffer sb) {
        if (isCommentLines(line)) {
            sb.append(line).append(System.lineSeparator());
            return;
        }

        String[] formatCodes = formatCode(line);

        for (String code : formatCodes) {
            if (isContainsPrintStatement(code)) {
                if (isCommentLines(code)) {
                    sb.append(code).append(System.lineSeparator());
                } else {
                    sb.append("//").append(code).append(System.lineSeparator());
                }
            } else {
                sb.append(code).append(System.lineSeparator());
            }
        }
    }

    /**
     * format the code
     *
     * @param line code
     * @return formatted code
     */
    private String[] formatCode(String line) {
        Map<String, String> map = new HashMap<>(16);
        String[] split = CodeFormatUtils.transferParamToUUid(line, "\"", map).split(statementSeparator);
        for (int i = 0; i < split.length; i++) {
            for (Map.Entry<String, String> r : map.entrySet()) {
                split[i] = split[i].replace(r.getKey(), r.getValue());
            }
            split[i] = split[i] + statementSeparator;
        }
        return split;
    }

    /**
     * whether is a comments line
     *
     * @param line line data.
     * @return whether is a comments line
     */
    private boolean isCommentLines(String line) {
        String lineTrim = line.trim();
        return lineTrim.startsWith("//")
                || lineTrim.startsWith("/*")
                || lineTrim.endsWith("*/")
                || lineTrim.startsWith("*");
    }
}
